import os
import ast
import logging
import re
from typing import Dict, List, Set, Tuple

from config.settings import CODE_EXTENSIONS, MAX_DEPTH

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


class CodeAnalyzer:
    """
    分析代码库并构建调用关系图
    """
    def __init__(self, repo_path: str):
        self.repo_path = os.path.abspath(repo_path)
        self.files = {}  # 文件内容缓存
        self.call_graph = {}  # 函数调用关系图
        self.imports = {}  # 导入关系
        self.file_functions = {}  # 每个文件定义的函数
        logger.info(f"初始化代码分析器，目标路径: {self.repo_path}")

    def analyze(self) -> Dict:
        """执行完整的代码分析流程"""
        logger.info("开始分析代码库...")
        self._scan_files()
        self._parse_imports_and_functions()
        self._build_call_graph()
        logger.info(f"代码分析完成，共发现 {len(self.files)} 个源代码文件")
        return {
            "call_graph": self.call_graph,
            "imports": self.imports,
            "file_functions": self.file_functions
        }

    def _scan_files(self) -> None:
        """扫描目录获取所有代码文件"""
        for root, _, files in os.walk(self.repo_path):
            for file in files:
                file_path = os.path.join(root, file)
                _, extension = os.path.splitext(file)
                if extension in CODE_EXTENSIONS:
                    relative_path = os.path.relpath(file_path, self.repo_path)
                    try:
                        with open(file_path, 'r', encoding='utf-8') as f:
                            content = f.read()
                            self.files[relative_path] = content
                    except UnicodeDecodeError:
                        logger.warning(f"无法解析文件: {file_path} - 编码错误")
                    except Exception as e:
                        logger.error(f"处理文件 {file_path} 时出错: {str(e)}")

    def _parse_imports_and_functions(self) -> None:
        """分析Python文件的导入和函数定义"""
        for file_path, content in self.files.items():
            if not file_path.endswith('.py'):
                continue
                
            try:
                tree = ast.parse(content)
                self.imports[file_path] = []
                self.file_functions[file_path] = []
                
                for node in ast.walk(tree):
                    # 处理导入语句
                    if isinstance(node, ast.Import):
                        for alias in node.names:
                            self.imports[file_path].append({
                                "module": alias.name,
                                "alias": alias.asname
                            })
                    elif isinstance(node, ast.ImportFrom):
                        module = node.module or ""
                        for alias in node.names:
                            self.imports[file_path].append({
                                "module": f"{module}.{alias.name}" if module else alias.name,
                                "alias": alias.asname,
                                "from_import": True
                            })
                    
                    # 处理函数定义
                    elif isinstance(node, ast.FunctionDef):
                        params = []
                        for arg in node.args.args:
                            param_name = arg.arg
                            # 尝试获取类型注解
                            if arg.annotation:
                                param_type = ast.unparse(arg.annotation)
                                params.append(f"{param_name}: {param_type}")
                            else:
                                params.append(param_name)
                        
                        # 尝试获取返回类型
                        return_type = ""
                        if node.returns:
                            return_type = f" -> {ast.unparse(node.returns)}"
                        
                        function_def = {
                            "name": node.name,
                            "params": params,
                            "return_type": return_type,
                            "docstring": ast.get_docstring(node) or ""
                        }
                        self.file_functions[file_path].append(function_def)
                        
            except SyntaxError:
                logger.warning(f"解析Python文件语法错误: {file_path}")
            except Exception as e:
                logger.error(f"分析文件 {file_path} 时出错: {str(e)}")

    def _build_call_graph(self) -> None:
        """构建函数调用关系图"""
        for file_path, content in self.files.items():
            if not file_path.endswith('.py'):
                continue
                
            try:
                # 初始化调用图
                if file_path not in self.call_graph:
                    self.call_graph[file_path] = {}

                tree = ast.parse(content)
                
                # 提取所有函数定义
                function_defs = {}
                for node in ast.walk(tree):
                    if isinstance(node, ast.FunctionDef):
                        function_defs[node.name] = node

                # 分析每个函数内的调用
                for func_name, func_node in function_defs.items():
                    self.call_graph[file_path][func_name] = []
                    
                    for node in ast.walk(func_node):
                        if isinstance(node, ast.Call):
                            if isinstance(node.func, ast.Name):
                                call_name = node.func.id
                                if call_name in function_defs and call_name != func_name:
                                    # 同一文件内的函数调用
                                    self.call_graph[file_path][func_name].append({
                                        "type": "local", 
                                        "name": call_name,
                                        "file": file_path
                                    })
                            elif isinstance(node.func, ast.Attribute):
                                # 可能是模块或对象方法调用
                                if isinstance(node.func.value, ast.Name):
                                    module_name = node.func.value.id
                                    method_name = node.func.attr
                                    
                                    # 尝试通过导入关系找到对应模块
                                    for import_info in self.imports.get(file_path, []):
                                        if import_info.get("alias") == module_name or import_info.get("module", "").split(".")[-1] == module_name:
                                            self.call_graph[file_path][func_name].append({
                                                "type": "external",
                                                "module": import_info.get("module", ""),
                                                "method": method_name
                                            })
            except Exception as e:
                logger.error(f"构建调用图时出错 {file_path}: {str(e)}")

    def get_context_for_file(self, file_path: str, depth=1) -> Dict:
        """获取特定文件的完整上下文信息"""
        if file_path not in self.files:
            return {"error": "File not found"}
            
        context = {
            "file_path": file_path,
            "content": self.files[file_path],
            "imports": self.imports.get(file_path, []),
            "functions": self.file_functions.get(file_path, []),
            "calls": {}
        }
        
        # 递归获取调用关系
        if file_path in self.call_graph and depth > 0:
            for func_name, calls in self.call_graph[file_path].items():
                context["calls"][func_name] = []
                
                for call in calls:
                    call_info = dict(call)  # 复制原始调用信息
                    
                    if call["type"] == "local" and depth > 1:
                        # 获取被调用函数的更多信息
                        called_file = call["file"]
                        called_func = call["name"]
                        if called_file in self.call_graph and called_func in self.call_graph[called_file]:
                            call_info["sub_calls"] = self.call_graph[called_file][called_func]
                    
                    context["calls"][func_name].append(call_info)
        
        return context

    def get_file_content(self, file_path: str) -> str:
        """获取指定文件的内容"""
        return self.files.get(file_path, "")
